# WTF Engine - Game entry point
# Woosta Table Football 2008

from WtfEngine import *
import WtfGui;
import WtfAudio;
from scripts import WtfObjs;
from scripts import WtfApp;
from scripts import WtfHud;
import math;
from scripts import GeneticAi;
# import Ogre;

DebugPrint("Script Begins");

mouseSensitivity = 0.025
controllerSensitivity = 1;
game = 0;
gWm = 0;
gMenu = 0;
gTable = 0;

#
# TODO:
# Game event handlers & game logic
#

class Menu(GameState):
	def __init__(self):
		GameState.__init__(self);
		
	
class Interface(WtfGui.ScreenOverlay):
	def __init__(self):
		
		WtfGui.ScreenOverlay.__init__(self);
		
		ctrOpts = WtfGui.LayoutContainerCreationOptions();
		self.font = WtfGui.Font("v_gui_font");
		
		# Create bars
		self.topBar = WtfGui.LayoutContainer(ctrOpts, 0);
		
		self.scoreBoard = WtfGui.Label("0 - 0", self.font);
		self.scoreBoard.ExpandToText();
						
		# Add elements
		self.AddChild(WtfGui.LayoutPositioner(self.topBar, WtfGui.CentrePositioner(), WtfGui.TopPositioner()));

		self.Init();
		self.topBar.AddChild(self.scoreBoard);
		
	def setGoals(self, a, b):
		self.scoreBoard.caption = str(a) + " - " + str(b);
		self.scoreBoard.ExpandToText();

class LocalGameWindow(WtfGui.Window):
	def __init__(self, font):
		WtfGui.Window.__init__(self, "Local Game");
		
		slider = WtfGui.Slider(0, 0, 100, 50, 1);
		slider.size = iVector2D(100, 20);
		self.AddChild(slider);
		

class Controller:
	def __init__(self, id, game):
		self.row = 2;
		self.angle = 0.0;
		self.hasDesiredAngle = False;
		self.position = 0.0;
		self.ax = 0.0;
		self.ay = 0.0;
		self.id = id;
		
		game.BindEvent( ControllerAxisEvent(id, 4), EventHandler(self.HoldSelect) );
		game.BindEvent( ControllerAxisEvent(id, 5), EventHandler(self.HoldSelect) );
		game.BindEvent( ControllerAxisEvent(id, 16), EventHandler(self.Select) );
		game.BindEvent( ControllerAxisEvent(id, 17), EventHandler(self.Select) );
		game.BindEvent( ControllerAxisEvent(id, 1), EventHandler(self.MovePos) );
		game.BindEvent( ControllerAxisEvent(id, 2), EventHandler(self.MoveAX) );
		game.BindEvent( ControllerAxisEvent(id, 3), EventHandler(self.MoveAY) );
	
	def MoveAX(self, e):
		self.ax = e.value;
		self.UpdateAngle();
	
	def MoveAY(self, e):
		self.ay = e.value;
		self.UpdateAngle();
	
	def MovePos(self, e):
		self.position = e.value;
	
	def UpdateAngle(self):
		self.hasDesiredAngle = (self.ax != 0.0 or self.ay != 0.0);
		if self.hasDesiredAngle:
			self.angle = WtfObjs.calcAngle(self.ax, self.ay);
	
	def Select(self, e):
		if e.value == 1.0:
			if e.axis == 17:
				self.row += 1;
				if self.row > 3:
					self.row = 0;
			else:
				self.row -= 1;
				if self.row < 0:
					self.row = 3;
	
	def HoldSelect(self, e):
		if e.value == 1.0:
			if self.row == 2:
				if e.axis == 4:
					self.row = 1;
				else:
					self.row = 3;
			else:
				self.row = 0;
		elif e.value == 0.0:
			if self.row == 0:
				if e.axis == 4:
					self.row = 3;
				else:
					self.row = 1;
			else:
				self.row = 2;

class ReplayState(GameState):
	def __init__(self, game, replay):
		GameState.__init__(self);
		self.replay = replay;
		self.game = game;
		
		self.BindEvent(KeyEvent(), EventHandler(lambda e: Kernel.GetInst().PopGameState()));
		self.BindEvent(ReplayStopEvent(replay), EventHandler(lambda e: Kernel.GetInst().PopGameState()));
		
		eh = EventHandler(lambda e: e.axis > 6 and Kernel.GetInst().PopGameState());
		for c in game.controllers:
			self.BindEvent(ControllerEvent(c.id), eh);
		
		self.lblReplay = WtfGui.Label("GOAL!\nAction Replay", game.interface.font);
	
	#def Initialize(self):
	#	DebugPrint("Showing Replay - " + str(self.replay.frameCount) + " frames");
	#	self.replay.Reset();
	#	self.replay.StartPlayback();
	#	self.game.interface.AddChild(self.lblReplay);
	
	#def Destroy(self):
	#	self.replay.StopPlayback();
	#	self.replay.Clear();
	#	self.lblReplay.Remove();


class Game(GameState):
	def __init__(self, table):
		GameState.__init__(self);
		
		self.camera = Camera(Vector(0,100,50),Vector(0,0,0),Vector(0,1,0));
		Renderer.GetInst().camera = self.camera;
		
		self.interface = Interface();
		
		self.plyrGoals = 0;
		self.otherGoals = 0;
		self.hasScored = 0;
		
		self.table = table;
		self.table.reset();
		
		self.rowHist = [];
		self.row = -1;
		
		self.controllers = [
			Controller(0, self), Controller(1, self)
		];
		
		self.keyMap = {
			'z': EventHandler(lambda x: self.toggleRow(x, 0)),
			'x': EventHandler(lambda x:	self.toggleRow(x, 1)),
			'c': EventHandler(lambda x:	self.toggleRow(x, 2)),
			'v': EventHandler(lambda x:	self.toggleRow(x, 3))
		};
		
		
		self.replay = Replay("wtfActionReplay");
		
		self.ball = WtfObjs.Ball();
		
		self.BindEvent(FrameStartEvent(), EventHandler(self.StartFrame));
		
		# Goal handlers
		self.BindEvent(ObjectInteractionEvent(self.ball, table.goal1), EventHandler(self.goalInAway));
		self.BindEvent(ObjectInteractionEvent(self.ball, table.goal2), EventHandler(self.goalInHome));
		
		# Mouse move event handler
		self.BindEvent(MouseMoveEvent(), EventHandler(self.moveRow));
		self.BindEvent(KeyDownEvent(InputKey(ord(' '))), EventHandler(lambda e: resetBall()));
		
		# Write keymap
		for key in self.keyMap.keys():
			k = InputKey(ord(key))
			self.BindEvent(KeyDownEvent(k), self.keyMap[key]);
			self.BindEvent(KeyUpEvent(k), self.keyMap[key]);

	def StartGame(self):
		self.interface.Init();
		Kernel.GetInst().PushGameState(game);
	
	def StartFrame(self, e):
		self.camera.direction = self.ball.position;
		p = self.camera.position;
		p.x = self.ball.position.x;
		self.camera.position = p;
		
		for c in self.controllers:
			row = self.table.rows[c.row + (c.id * 4)];
			row.moveTo(-c.position);		
			
			if c.hasDesiredAngle:
				row.rotateTo(c.angle);
		
	def showReplay(self):
		# Kernel.GetInst().PushGameState(ReplayState( self, self.replay ));
		return 0;
		
		
	def goalInHome(self, e):
		if self.hasScored == 0:
			DebugPrint("AWAY GOAL");
			self.otherGoals = self.otherGoals + 1;
			self.interface.setGoals(self.plyrGoals, self.otherGoals);
			self.hasScored = 1;
			self.showReplay();
			
	def goalInAway(self, e):
		if self.hasScored == 0:
			self.plyrGoals = self.plyrGoals + 1;
			DebugPrint("HOME GOAL [" + str(self.plyrGoals) + "]");
			self.interface.setGoals(self.plyrGoals, self.otherGoals);
			self.hasScored = 1;
			self.showReplay();
		
	def moveRow(self, e):
		if self.row != -1:
			self.table.rows[self.row].move((e.dy) * mouseSensitivity)
			self.table.rows[self.row].rotate((e.dx) * mouseSensitivity)
	
	
	#
	# Set row i to the moving row
	#
	def startRow(self, i):
		DebugPrint("St " + str(i));
		
		if self.row != i:
			if self.rowHist.count(i) > 0:
				self.rowHist.remove(i);
			self.rowHist.append(self.row);
			self.row = i;
		
	
	#
	# Stop moving row i
	#
	def endRow(self, i):
		DebugPrint("End " + str(i));
		
		if self.row == i:
			self.row = self.rowHist.pop();
		else:
			self.rowHist.remove(i);
		
		
	def toggleRow(self, e, i):
		if isinstance(e, KeyDownEvent):
			self.startRow(i);
		else:
			self.endRow(i);
	
	def Enable(self):
		DebugPrint("Enabling Game..");
		InputHandler.GetInst().CaptureAllInput(1);
		self.oldRate = PhysicsEngine.GetInst().rate;
		PhysicsEngine.GetInst().rate = 250.0;	# Run physics at 250Hz
		DebugPrint("Game Enabled - physics at 250Hz");
		self.replay.StartRecording(600);
	
	def Disable(self):
		PhysicsEngine.GetInst().rate = self.oldRate;
		Kernel.GetInst().GetGameState().timeScale = 0.0;
		DebugPrint("Game Disabled - physics at "+str(self.oldRate)+"Hz");
		self.replay.StopRecording();

class AiBreeder(Game):
	def __init__(self, table):
		Game.__init__(self, table);
		
		self.breeder = GeneticAi.Breeder(2, 64, [0,1,2,3,4,5,6], [61,62,63]);
		self.breeder.numMutations = 16;
	
	def ProcessInput(self, rid, pos, angle):
		row = self.table.rows[rid];
		row.moveTo(pos);
		row.rotateTo(angle);
		
	def ProcessAiInput(self, ai, offs, side):
		vin = FloatArray();
		vin.extend([self.ball.position.x, self.ball.position.y, self.ball.position.z,
			self.ball.velocity.x, self.ball.velocity.y, self.ball.velocity.z, side]);
		ai.ProcessInputs(vin);
		
		vs = FloatArray();
		vs.extend([0.0, 0.0, 0.0]);
		ai.ReadOutputs(vs);
		
		if vs[0] < -0.5:
			i = 0;
		elif vs[0] < 0:
			i = 1;
		elif vs[0] < 0.5:
			i = 2;
		else:
			i = 3;
		
		self.ProcessInput(offs + i, vs[1], vs[2]);
		
		
	def StartFrame(self, e):
		Game.StartFrame(self, e);
		if self.ball.position.y < -1.0 or self.ball.position.y > 1000.0:
			self.ball.reset();
		self.ProcessAiInput(self.breeder.population[0], 0, 1.0);
		self.ProcessAiInput(self.breeder.population[1], 4, -1.0);
	
	def goalInHome(self, e):
		self.breeder.NextGeneration(self.breeder.population[1], False);
		self.ball.reset();
		
	def goalInAway(self, e):
		self.breeder.NextGeneration(self.breeder.population[0], False);
		self.ball.reset();

class AiTrainer(Game):
	def __init__(self, table):
		Game.__init__(self, table);
		
		vin = UnsignedArray();
		vin.extend([0,1,2,3,4,5]);
		out = UnsignedArray();
		out.extend([61,62,63]);
		
		#self.ai = GeneticAi.WtfAi.GeneticNeuralNetwork("ai_trainer", 64, vin, out);
		#self.ai.Randomize();
		self.ai = GeneticAi.WtfAi.GeneticNeuralNetworkRef("ai-trainer.txt");
		self.angleMult = math.pi;
	
	def SaveAi(self):
		self.ai.SaveToFile("ai-trainer.txt");
		
	def ProcessInput(self, rid, pos, angle):
		row = self.table.rows[rid];
		row.moveTo(pos);
		row.rotateTo(angle);
	
	def ProcessAiInput(self, side):
		vin = FloatArray();
		vin.extend([self.ball.position.x * side, self.ball.position.y * side, self.ball.position.z * side,
			self.ball.velocity.x * side, self.ball.velocity.y * side, self.ball.velocity.z * side]);
		self.ai.ProcessInputs(vin);
	
	def ProcessAi(self, offs, side):
		self.ProcessAiInput(side);
		
		vs = FloatArray();
		vs.extend([0.0, 0.0, 0.0]);
		self.ai.ReadOutputs(vs);
		
		if vs[0] < -0.5:
			i = 0;
		elif vs[0] < 0:
			i = 1;
		elif vs[0] < 0.5:
			i = 2;
		else:
			i = 3;
		
		self.ProcessInput(offs + i, vs[1] * side, vs[2] * side * self.angleMult);
		
	def Backpropagate(self, c, side):
		self.ProcessAiInput(side);
		
		if c.row == 0:
			sel = -0.75;
		elif c.row == 1:
			sel = -0.25;
		elif c.row == 2:
			sel = 0.25;
		else:
			sel = 0.75;
			
		vs = FloatArray();
		vs.extend([sel, c.position, c.angle / self.angleMult]);
		self.ai.BackPropagate(vs, 0.1);
		
	def StartFrame(self, e):
		Game.StartFrame(self, e);
		self.ProcessAi(4, -1.0);
		self.Backpropagate(self.controllers[0], 1.0);
		#self.Backpropagate(self.controllers[1], -1.0);
		
	def Destroy(self):
		self.SaveAi();


class GameOptions(WtfGui.Window):
	def __init__(self, game):
		WtfGui.Window.__init__("Game Options");
		
		self.game = game;
	
	def Start(self):
		self.game.Start();
		
		
def mainMenu():
	global menu;
	menu = Menu();
	getKernel.ForkGameState(menu);
	
def startGame():
	global game; global gTable;
	game = Game(gTable);
	Kernel.GetInst().PushGameState(game);
	menu = GameMenu();
	menu.BindToEvent(KeyDownEvent(InputKey(27)));

def breedAi():
	global game; global gTable;
	game = AiBreeder(gTable);
	Kernel.GetInst().PushGameState(game);
	menu = GameMenu();
	menu.BindToEvent(KeyDownEvent(InputKey(27)));

def trainAi():
    global game; global gTable;
    game = AiTrainer(gTable);
    Kernel.GetInst().PushGameState(game);
    menu = GameMenu();
    menu.BindToEvent(KeyDownEvent(InputKey(27)));

def resetBall():
	global game;
	game.ball.reset();
	game.hasScored = 0;

class Options(WtfGui.Window):
	def __init__(self, font):
		WtfGui.Window.__init__(self, "Options");
		
		self.text = WtfGui.Label("Coming soon", font);
		self.AddChild(self.text);
		
		self.text.ExpandToText();
		
		self.cp = WtfGui.ColourPicker();
		self.cp.size = iVector2D(100, 250);
		self.AddChild(self.cp);
		
class GuiHelp(WtfGui.Window):
	def __init__(self, font):
		WtfGui.Window.__init__(self, "GUI Help");
		
		self.text = WtfGui.Label("Keyboard Shortcuts\n ESC - close the GUI\n Ctrl + Tab - switch windows\n Ctrl + W - close window\n Tab - cycle forwards\n Shift + Tab - cycle backwards", font);
		self.AddChild(self.text);
		
		self.text.ExpandToText();

def spawnUtility(wm):
	wnd = WtfGui.Window("Utility");
	wnd.innerSize = iVector2D(400, 400);
	scene = WtfGui.Scene(
		Camera(Vector(0, 100, 100), Vector(0, -100, -100), Vector(0, 1, 0)),
		iVector2D(400, 350)
	);
	btnSShot = WtfGui.Button(
		Function(lambda: scene.frameBuffer.Write("screenie.png")),
		"Save Screenshot", WtfGui.Font("v_gui_font")
	);
	
	wnd.AddChild(scene);
	
	btnSShot.Move(0, 350);
	btnSShot.size = iVector2D(200, 30);
	wnd.AddChild(btnSShot);
	
	wm.AddWindow(wnd, 1);


class GameMenu(WtfHud.GameMenu):
	def __init__(self):
		WtfHud.GameMenu.__init__(self, {
			'Options': (lambda: self.AddWindow(Options(font), 1)),
			'Developers Console': (lambda: self.SpawnConsole()),
			'Save Game': (lambda: GameManager.GetInst().SaveGameToFile("f.txt")),
			'Load Game': (lambda: GameManager.GetInst().LoadGameFromFile("f.txt")),
			'Exit': (lambda: exit())
		}, ['Developers Console', 'Save Game', 'Load Game', 'Exit']);

def initGUI():
	font = WtfGui.Font("v_gui_font");
	
	# Set colours
	# WtfGui.Colour("v_gui_bgColour").value = ColourVal(0, 0, 0, 0.5);
	WtfGui.Colour("v_gui_windowBgColour").value = ColourVal(0.3, 0.29, 0.29, 1.0);
	WtfGui.Colour("v_gui_wmBg").value = ColourVal(0.2,0.2,0.2,0.5);
	
	# Create menu
	gWm = WtfHud.TitleMenu({
		'Local Game': lambda: gWm.AddWindow(LocalGameWindow(font), 1),
		'Network Game': lambda: WtfLAN.showLobby(gWm),
		'Options': lambda: gWm.AddWindow(Options(font), 1),
		'Developers Console': lambda: gWm.SpawnConsole(),
		'WtfEdit': lambda: WtfApp.Load("wtfedit", {'wm': gWm}),
		'WtfRTS': (lambda: WtfApp.Load("rts", {"wm": gWm})),
		'Breed AI': breedAi,
        'Train AI': trainAi,
		'Exit': lambda: exit()
	}, ['Local Game','Network Game','Breed AI','Train AI','Options','Developers Console','WtfEdit','WtfRTS','Exit']);
	gWm.BindToEvent(KeyDownEvent(InputKey(27,0)));

	# Activate the GUI
	gWm.WmToggle();


def startIntro():
	r = Renderer.GetInst();
	backdrop = WtfGui.ScreenOverlay();
	
	wm = WtfGui.WindowManager();
	wm.WmToggle();
	
	img = WtfGui.Image(TextureRef("media/images/DynamoGameStudios.png"));
	img.innerSize = iVector2D(500,360);
	wm.AddChild(WtfGui.LayoutPositioner(img, WtfGui.CentrePositioner(), WtfGui.CentrePositioner()));
	
	opts = WtfGui.ColourBackgroundCreationOptions();
	c = WtfGui.ColourSetting("intro_backdrop_colour", ColourVal(0,0,0,1.0));
	opts.colour = c;
	backdrop.background = WtfGui.BackgroundSetting("intro_backdrop",
		WtfGui.ColourBackground(opts));
	backdrop.size = wm.size;
	backdrop.Init();
	
	def startMenu():
		backdrop.Remove();
		wm.WmToggle();
		initGUI();
	
	def logoSwitch(logo, f):
		img.src = TextureRef(logo);
		c.interpolate(ColourVal(0,0,0,0), 3.5, Function(f));
	
	def out(f):
		c.interpolate(ColourVal(0,0,0,1), 3.5, Function(f));
	
	fIn = (lambda: c.interpolate(ColourVal(0,0,0,0), 3.5, Function(
		lambda: out(lambda: logoSwitch("media/images/PoweredBy.png",
			lambda: out(lambda: logoSwitch("media/images/WTF.png", startMenu))))
		)));
	c.interpolate(ColourVal(0,0,0,1), 3.0, Function(fIn));



## Initialization starts here

DebugPrint("Window manager initialized");

gTable = WtfObjs.Table();
camOpts = PannableCameraOptions();
c = PannableCamera(camOpts, Vector(0, 100, 100), Vector(0, -100, -100));
Renderer.GetInst().camera = c;
c.BindToKeyboardAndMouse();
		
font = WtfGui.Font("v_gui_font");
initGUI();
#startIntro();
#startGame();
# WtfLAN.test();
#WtfApp.Load("tbs", []);
#WtfApp.Load("synth", []);

# Intro sound
# WtfAudio.AudioStream(WtfAudio.VorbisData("media/music/1991.ogg")).Play(False);

DebugPrint("Game initialized!");
